"""
General utilities and definitions for the spoc project
"""

import sys
import os
import re
import csv
import ConfigParser

from logger import logger

#
# general definitions (global variables)
#

MULTIPLIER_DIMENSION = 4
ADDER_DIMENSION = 4
SUBTRACTOR_DIMENSION = 4
MEMORY_DIMENSION = 4
DEVICE_DIMENSION = 6
STRUCT_DIMENSION = 4
CELL_DIMENSION = 4
NUM_ELEMENT_INPUTS = 4
ROWS_PER_ELEMENT = 2 ** NUM_ELEMENT_INPUTS
COLS_PER_ELEMENT = 2

#
# error handling for util functions
#

class UtilError(Exception):
    """
    handling class for utility exceptions
    """
    
    def __init__(self, msg):
        """
        initialize the class
        """
        logger.error("utils: %s" % msg)

#
# library support
#

element_type_dict = {}
cell_type_dict = {}
module_dict = {}

element_dir = os.path.join(os.pardir, 'lib', 'element')
celltype_dir = os.path.join(os.pardir, 'lib', 'cell')
module_dir = os.path.join(os.pardir, 'lib', 'module')

class ModuleType:
    """
    Simple structure for holding module type information
    """
    
    def __init__(self, filename):
        """
        Define the structure members by reading from a .module file
        """
        #initialize members
        self.cells = []
        self.names = []
        self.inputs = []
        self.outputs = []
        self.routing = ''
        
        #read the file contents
        config = ConfigParser.RawConfigParser()
        config.readfp(open(filename))
        
        #get the execution mode (math or memory)
        if config.has_option('module', 'mode'):
            self.mode = config.get('module', 'mode')
        else:
            self.mode = ''
        
        #extract the cell type info
        csv_reader = csv.reader(config.get('module', 'cells').rsplit())
        for row in csv_reader:
            self.cells.append(row)
        
        #get the cell names
        csv_reader = csv.reader(config.get('module', 'names').rsplit())
        for row in csv_reader:
            self.names.append(row)
        
        #extract the input specification
        i = 1
        while config.has_option('module', 'in' + str(i)):
           raw_input = config.get('module', 'in' + str(i))
           csv_reader = csv.reader(raw_input.rsplit())
           in_group = []
           for row in csv_reader:
               in_group.append(row)
           self.inputs.append(in_group)
           i += 1
           
        #extract the output specification
        self.outputs = config.get('module', 'out').split(',')
                                       
        #save off routing information
        self.routing = config.get('module', 'routing')

        #check values
        if(len(self.cells) != len(self.names)):
            raise UtilError('cell array and name array are different heights')
        cell_row_len = len(self.cells[0])
        for row in self.cells:
            if len(row) != cell_row_len:
                raise UtilError('inconsistent cell row length')
        name_row_len = len(self.names[0])
        for row in self.names:
            if len(row) != name_row_len:
                raise UtilError('inconsistent name row length')
        if cell_row_len != name_row_len:
            raise UtilError('name row length does not match cell row length')

class CellType:
    """
    Simple structure for holding cell type information
    """

    def __init__(self, filename):
        """
        Define the structure members by reading from a .cell file
        """
        #initialize members
        self.elements = []
        self.inputs = []
        self.outputs = []

        #read the file contents
        config = ConfigParser.RawConfigParser()
        config.readfp(open(filename))

        #extract the library info
        csv_reader = csv.reader(config.get('cell', 'elements').rsplit())
        for row in csv_reader:
            self.elements.append(row)
        #self.inputs = config.get('cell', 'inputs').split(',')
        #self.outputs = config.get('cell', 'outputs').rsplit()

def load_libs():
    """
    reads the contents of library files into dictionaries
    
    mostly useful for 'refreshing' the libraries
    """
    load_module_lib()
    load_cell_lib()
    load_element_lib()

def load_module_lib():
    """
    reads the contents of the module library files into a dictionary
    """
    for filename in os.listdir(module_dir):
        filesplit = re.split("\.", filename)
        #for each .module file
        if filesplit[1] == "module":
            #add entry to the module dictionary
            module_dict[filesplit[0]] = ModuleType(os.path.join(module_dir, filename))

def load_cell_lib():
    """
    reads the contents of the celltype library files into a dictionary
    """
    for filename in os.listdir(celltype_dir):
        filesplit = re.split("\.", filename)
        #for each .cell file
        if filesplit[1] == "cell":
            #add entry to the cell type dictionary
            cell_type_dict[filesplit[0]] = CellType(os.path.join(celltype_dir, filename))
        
def load_element_lib():
    """
    reads the contents of the LUT library files into a dictionary
    """
    for filename in os.listdir(element_dir):
        filesplit = re.split("\.", filename)
        #for each .lut file
        if filesplit[1] == 'lut':
            #clear the LUT array
            lut_array = []
            #read the contents
            csv_reader = csv.reader(open(os.path.join(element_dir, filename)))
            for row in csv_reader:
                #ignore comments
                if row[0][0] != '#' and row != '':
                    #add to the LUT array
                    lut_array.append(row)
            #add the LUT array to the LUT dictionary
            element_type_dict[filesplit[0]] = lut_array

#
# functions for manipulating bitstrings
#

def slice_string(string, chunksize = CELL_DIMENSION):
    """
    slice a string into chunks, returning a list
    """
    sliced_string = []
    for i in range(len(string)/chunksize):
        sliced_string.append(string[:chunksize])
        string = string[chunksize:]
    return sliced_string

def bitstring_add(bitstring_a, bitstring_b, carry_in):
    """
    find the sum of two bitstrings
    """
    #bitstrings must be of equal length
    if len(bitstring_a) != len(bitstring_b):
        raise UtilError('bitstring length mismatch')
    
    new_bitstring = ''
    carry = carry_in
    for bit_a, bit_b in zip(bitstring_a[::-1], bitstring_b[::-1]):
        abc = (bit_a, bit_b, carry)
        if   abc == ('0','0','0'):
            new_bitstring = '0' + new_bitstring
            carry = '0'
        elif abc == ('0','0','1'):
            new_bitstring = '1' + new_bitstring
            carry = '0'
        elif abc == ('0','1','0'):
            new_bitstring = '1' + new_bitstring
            carry = '0'
        elif abc == ('0','1','1'):
            new_bitstring = '0' + new_bitstring
            carry = '1'
        elif abc == ('1','0','0'):
            new_bitstring = '1' + new_bitstring
            carry = '0'
        elif abc == ('1','0','1'):
            new_bitstring = '0' + new_bitstring
            carry = '1'
        elif abc == ('1','1','0'):
            new_bitstring = '0' + new_bitstring
            carry = '1'
        elif abc == ('1','1','1'):
            new_bitstring = '1' + new_bitstring
            carry = '1'
        else:
            raise UtilError('non-binary in character string')
    return (carry, new_bitstring)

def bitstring_mul(bitstring_a, bitstring_b):
    """
    find the product of two bitstrings
    """
    #bitstrings must be of equal length
    if len(bitstring_a) != len(bitstring_b):
        raise UtilError('bitstring length mismatch')
    
    result = ''
    carry_in = '0'
    
    for bit in bitstring_a[::-1]:
        if bit == '1':
            result = bitstring_add(result, bitstring_b)
            result = '0' + result
            bitstring_b = '0'
    
    return result[0:2*len(bitstring_a)]

